#!/usr/bin/python3
# -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t -*-

import os
import sys
import signal
import shutil
import socket
import logging
import subprocess
import strict_openssh
from gi.repository import GLib
from gbs_util import GbsUtil
from gbs_util import AvahiServiceRegister
from gbs_util import UnixDomainSocketApiServer
from gbs_param import GbsConst


class GbsDaemon:

    def __init__(self, param):
        self.param = param

        self.apiServer = None
        self.sessionDict = dict()

        self.mainServerProc = None
        self.avahiObj = None
        self.mainloop = None

    def run(self):
        if not os.path.exists(GbsConst.varDir):
            os.makedirs(GbsConst.varDir)
        GbsUtil.mkDirAndClear(self.param.tmpDir)
        GbsUtil.mkDirAndClear(GbsConst.runDir)

        try:
            logging.getLogger().addHandler(logging.StreamHandler(sys.stderr))
            logging.getLogger().setLevel(GbsUtil.getLoggingLevel(self.param.logLevel))
            logging.info("Program begins.")

            # create main loop
            self.mainloop = GLib.MainLoop()

            # write pid file
            with open(self.param.pidFile, "w") as f:
                f.write(str(os.getpid()))

            # start API server (for both interal and external use)
            self.apiServer = UnixDomainSocketApiServer(self.param.apiServerFile, self._clientAppearFunc, self._clientDisappearFunc, self._clientRequestFunc)
            logging.info('API server started.')

            # start main server (which is an openssh server with custom subsystem)
            obj = strict_openssh.ServerSysCfg(cfg_dir=os.path.join(GbsConst.runDir[1:], "ssh"))     # [1:] is to eliminate the first "/"
            obj.generate_host_key("rsa")
            obj.set_listen_addresses(["0.0.0.0"])
            obj.set_port(self.param.ctrlPort)
            obj.set_printing_motd(False)
            obj.set_printing_lastlog(False)
            obj.set_exposing_auth_info(True)
            obj.set_force_command_exec_line(GbsConst.sshSessionExec)
            obj.accept_environment_variable("GBS_SESSION_TYPE")
            obj.set_auth_info("publickey", key_command_script_type=strict_openssh.AuthInfoPublickey.KeyCommandScriptType.PERMIT_ALL)
            obj.disable_tcp_port_forwarding()
            self.mainServerProc = subprocess.Popen(["/usr/bin/sshd", "-D", "-e", "-f", obj.cfg_file_path])      # FIXME: sshd requires absolute binary path, why other program does not need this?
            logging.info('Main server started.')

            # register serivce
            if GbsConst.avahiSupport:
                self.avahiObj = AvahiServiceRegister()
                self.avahiObj.add_service(socket.gethostname(), "_syncup._tcp", self.param.ctrlPort)
                self.avahiObj.start()

            # start main loop
            logging.info("Mainloop begins.")
            GLib.unix_signal_add(GLib.PRIORITY_HIGH, signal.SIGINT, self._sigHandlerINT, None)
            GLib.unix_signal_add(GLib.PRIORITY_HIGH, signal.SIGTERM, self._sigHandlerTERM, None)
            GLib.unix_signal_add(GLib.PRIORITY_HIGH, signal.SIGUSR2, self._sigHandlerUSR2, None)    # let the process ignore SIGUSR2, we
            signal.siginterrupt(signal.SIGUSR2, True)                                               # use it to interrupt blocking system calls
            self.mainloop.run()
            logging.info("Mainloop exits.")
        finally:
            if self.avahiObj is not None:
                self.avahiObj.stop()
            if self.mainServerProc is not None:
                self.mainServerProc.terminate()
                self.mainServerProc.wait()
            if self.apiServer is not None:
                self.apiServer.dispose()
            logging.shutdown()
            shutil.rmtree(GbsConst.runDir)
            shutil.rmtree(self.param.tmpDir)
            logging.info("Program exits.")

    def _sigHandlerINT(self, signum):
        logging.info("SIGINT received.")
        self.mainloop.quit()
        return True

    def _sigHandlerTERM(self, signum):
        logging.info("SIGTERM received.")
        self.mainloop.quit()
        return True

    def _sigHandlerUSR2(self, signum):
        return True

    def _clientAppearFunc(self, sock):
        assert sock not in self.sessionDict
        self.sessionDict[sock] = None
        return sock

    def _clientDisappearFunc(self, sock):
        logging.info("%s disappeared." % (self._toDebugStr(self.sessionDict[sock])))
        del self.sessionDict[sock]

    def _clientRequestFunc(self, sock, data):
        if self.sessionDict[sock] is None:
            clientIp = data["client-ip"]
            clientPubKey = data["client-pubkey"]
            self.sessionDict[sock] = GbsCtrlSession(self, clientPubKey)
            logging.info("Main Server: Client \"%s\" appeared, UUID \"%s\"." % (clientIp, self.sessionDict[sock].sysObj.getUuid()))
            return None

        return self.sessionDict[sock].onRequest(data)
